// Ofri Keidar 302933981 group 05

package server;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import model.apartment.Apartment;
import model.apartment.Flat;
import model.apartment.GardenApartment;
import model.apartment.ParameterParser;
import model.apartment.Penthouse;
import model.database.DBException;

import controller.DBController;
import controller.Message;

/**
 * Class adapts server's messages to database-system's controller interface:
 * gets client's message from server-controller and activates the database-system's controller accordingly
 * and converts system's Controller's result to a message 
 */
public class ServerDBControllerAdapter {
	
	/**
	 * Interface to represent a query to the database- gets parameters and returns result.
	 * Query made is according to implementing class
	 */
	private interface DBquery {
		
		/**
		 * Gets parameters as a map or as a list of maps, and returns result. 
		 * Uses database-system's controller object in order to perform the query
		 * @param queryParameters- map of parameters for the query
		 * @param multipleParameters- list of parameters maps for the query
		 * @param transactor- database-system's controller
		 * @return If result is a list of apartments, returned value is a list of maps,
		 * 		   that each map holds the parameters of an apartment.
		 *         Otherwise, stores the string representation of the result in a map and returns a
		 *         list holding this map.
		 * @throws DBException- throws database exception if database encountered a problem
		 */
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException; 
	}
	
	/**
	 * Class that implements requesting from server the vacant flats 
	 */
	private class getVacantFlats implements DBquery {
		
		/**
		 * Uses database-system's controller in order to get
		 * the vacant flats in system.
		 * Returns parameter map of each flat  
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// get all vacant flats in system
			List<Flat> vacantFlats = transactor.getVacantFlats();
			
			// convert result to message content
			return convertFlatsToParamsMap(vacantFlats);
		}
	}
	
	/**
	 * Class that implements requesting from server the vacant flats
	 * with given room number 
	 */
	private class getVacantFlatsByRooms implements DBquery {
		
		/**
		 * Uses database-system's controller in order to get
		 * the vacant flats with given room number in system.
		 * Returns parameter map of each flat  
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// check if necessary parameter was given
			if (!queryParameters.containsKey(Message.NUM_OF_ROOMS)) {
				
				// wrong parameters
				return null;
			}
			
			// extract room number
			double roomNumber = 0;
			try {
				roomNumber = Double.parseDouble(queryParameters.get(Message.NUM_OF_ROOMS));
			} catch (NumberFormatException e) {

				// wrong parameters
				return null;
			}
			
			// get all vacant flats with given room number
			List<Flat> vacantFlats = transactor.getVacantFlats(roomNumber);
			
			// convert result to message content
			return convertFlatsToParamsMap(vacantFlats);
		}
	}
	
	/**
	 * Class that implements requesting from server the vacant flats
	 * with given room number at given address 
	 */
	private class getVacantFlatsByRoomsAndAddress implements DBquery {
	
		/**
		 * Uses database-system's controller in order to get
		 * the vacant flats with given room number at given address.
		 * Returns parameter map of each flat
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// check if necessary parameters were given
			if (!queryParameters.containsKey(Message.NUM_OF_ROOMS)
					|| !queryParameters.containsKey(Message.ST_NAME)
					|| !queryParameters.containsKey(Message.BLD_NUMBER)) {
				
				// wrong parameters
				return null;
			}
			
			// extract room and building number
			double roomNumber = 0;
			int bldNumber = 0;
			try {
				roomNumber = Double.parseDouble(queryParameters.get(Message.NUM_OF_ROOMS));
				bldNumber = Integer.parseInt(queryParameters.get(Message.BLD_NUMBER));
			} catch (NumberFormatException e) {

				// wrong parameters
				return null;
			}
			
			// get all vacant flats with given room number at given address
			List<Flat> vacantFlats = transactor.getVacantFlats(roomNumber, queryParameters.get(Message.ST_NAME), bldNumber);
			
			// convert result to message content
			return convertFlatsToParamsMap(vacantFlats);
		}
	}

	/**
	 * Class that implements requesting from server the flats
	 * at given address 
	 */
	private class getFlatsByAddress implements DBquery {
		
		/**
		 * Uses database-system's controller in order to get
		 * the flats at given address in system.
		 * Returns parameter map of each flat
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// check if necessary parameters were given
			if (!queryParameters.containsKey(Message.ST_NAME)
					|| !queryParameters.containsKey(Message.BLD_NUMBER)) {
				
				// wrong parameters
				return null;
			}
			
			// extract building
			int bldNumber = 0;
			try {			
				bldNumber = Integer.parseInt(queryParameters.get(Message.BLD_NUMBER));
			} catch (NumberFormatException e) {

				// wrong parameters
				return null;
			}
			
			// get all flats at given address
			List<Flat> vacantFlats = transactor.getFlats(queryParameters.get(Message.ST_NAME), bldNumber);
			
			// convert result to message content
			return convertFlatsToParamsMap(vacantFlats);
		}
	}
	
	/**
	 * Class that implements requesting from server all flats in system 
	 */
	private class getAllFlats implements DBquery {
		
		/**
		 * Uses database-system's controller in order to get
		 * the all flats in system.
		 * Returns parameter map of each flat
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// get all flats in system
			List<Flat> flats = transactor.getFlats();
			
			// convert result to message content
			return convertFlatsToParamsMap(flats);
		}
	}
	
	/**
	 * Class that implements requesting from server all penthouses in system 
	 */
	private class getAllPenthouses implements DBquery {
		
		/**
		 * Uses database-system's controller in order to get
		 * the all penthouses in system.
		 * Returns parameter map of each penthouse
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// get all penthouses in system
			List<Penthouse> penthouses = transactor.getPenthouses();
			
			// convert result to message content
			return convertPenthousesToParamsMap(penthouses);
		}
	}
	
	/**
	 * Class that implements requesting from server all garden apartments
	 * in system 
	 */
	private class getAllGardenApartments implements DBquery {
		
		/**
		 * Uses database-system's controller in order to get
		 * the all garden apartments in system.
		 * Returns parameter map of each garden apartment
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// get all garden apartments in system
			List<GardenApartment> gardenApartments = transactor.getGardenApartments();
			
			// convert result to message content
			return convertGardenApartmentsToParamsMap(gardenApartments);
		}
	}
	
	/**
	 * Class that implements requesting from server number of
	 * all vacant apartments in system 
	 */
	private class getVacantAprtsNum implements DBquery {

		/**
		 * Uses database-system's controller in order to get
		 * number of all vacant apartments in system.
		 * Wraps string representation of the numeric result in a container,
		 * according to method's return value
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// get number of vacant apartments
			int vacantAprtsNum = transactor.getVacantAprtsNum();
			
			// create wrapper container for result
			Map<String, String> resultWrapper = new HashMap<String, String>();
			resultWrapper.put(Message.NUMERIC_VALUE, ""+vacantAprtsNum);
			List<Map<String,String>> retValWrapper = new LinkedList<Map<String,String>>();
			retValWrapper.add(resultWrapper);

			// return the wrapped value
			return retValWrapper;
		}
	}
	
	/**
	 * Class that implements requesting from server number of
	 * all vacant apartments at given address 
	 */
	private class getVacantAprtsNumByAddress implements DBquery {
		
		/**
		 * Uses database-system's controller in order to get
		 * number of all vacant apartments at given address.
		 * Wraps string representation of the numeric result in a container,
		 * according to method's return value
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// check if necessary parameters were given
			if (!queryParameters.containsKey(Message.ST_NAME)
					|| !queryParameters.containsKey(Message.BLD_NUMBER)) {
				
				// wrong parameters
				return null;
			}
			
			// extract building number			
			int bldNumber = 0;
			try {			
				bldNumber = Integer.parseInt(queryParameters.get(Message.BLD_NUMBER));
			} catch (NumberFormatException e) {

				// wrong parameters
				return null;
			}
			
			// get number of vacant apartments at given address
			int vacantAprtsNum = transactor.getVacantAprtsNum(queryParameters.get(Message.ST_NAME), bldNumber);
			
			// create wrapper container for result
			Map<String, String> resultWrapper = new HashMap<String, String>();
			resultWrapper.put(Message.NUMERIC_VALUE, ""+vacantAprtsNum);
			List<Map<String,String>> retValWrapper = new LinkedList<Map<String,String>>();
			retValWrapper.add(resultWrapper);

			// return wrapped value
			return retValWrapper;
		}
	}
	
	/**
	 * Class that implements adding new building to system, according to
	 * given parameters 
	 */
	private class addBuildings implements DBquery {
	
		/**
		 * Uses database-system's controller in order to add building according
		 * to given parameters.
		 * Wraps string representation of boolean indicator of query's success in a container,
		 * according to method's return value
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// check if necessary parameter was given
			if (!queryParameters.containsKey(Message.ST_NAME) || !queryParameters.containsKey(Message.ST_NAME)) {
				
				// wrong parameters
				return null;
			}			
			
			// extract building number			
			int bldNumber = 0;
			try {			
				bldNumber = Integer.parseInt(queryParameters.get(Message.BLD_NUMBER));
			} catch (NumberFormatException e) {

				// wrong parameters
				return null;
			}
			
			// add building
			boolean couldAddBuildings = transactor.addBuilding(multipleParameters, queryParameters.get(Message.ST_NAME), bldNumber);
			
			// create wrapper container for result
			Map<String, String> resultWrapper = new HashMap<String, String>();
			resultWrapper.put(Message.BOOLEAN_VALUE, ""+couldAddBuildings);
			List<Map<String,String>> retValWrapper = new LinkedList<Map<String,String>>();
			retValWrapper.add(resultWrapper);

			// return wrapped value
			return retValWrapper;
		}
	}
	
	/**
	 * Class that implements updating resident name, according to
	 * given parameters 
	 */
	private class updateResident implements DBquery {
		
		/**
		 * Uses database-system's controller in order to update resident name according
		 * to given address, apartment number and new resident name.
		 * Wraps string representation of boolean indicator of query's success in a container,
		 * according to method's return value
		 */
		@Override
		public List<Map<String, String>> doQuery(
				Map<String, String> queryParameters,
				List<Map<String, String>> multipleParameters,
				DBController transactor) throws DBException {
			
			// check if necessary parameters were given
			if (!queryParameters.containsKey(Message.ST_NAME)
					|| !queryParameters.containsKey(Message.BLD_NUMBER)
					|| !queryParameters.containsKey(Message.APARTMENT_NUMBER)
					|| !queryParameters.containsKey(Message.NEW_RESIDENT_NAME)) {
				
				// wrong parameters
				return null;
			}
			
			// extract parameters
			String stName = queryParameters.get(Message.ST_NAME);
			String newResident = queryParameters.get(Message.NEW_RESIDENT_NAME);
			int bldNumber = 0, apartmentNum = 0;
			try {			
				bldNumber = Integer.parseInt(queryParameters.get(Message.BLD_NUMBER));
				apartmentNum = Integer.parseInt(queryParameters.get(Message.APARTMENT_NUMBER));
			} catch (NumberFormatException e) {

				// wrong parameters
				return null;
			}
			
			// update resident name
			boolean couldUpdateResident = transactor.updateResident(stName, bldNumber, apartmentNum, newResident);
			
			// create wrapper container for result
			Map<String, String> resultWrapper = new HashMap<String, String>();
			resultWrapper.put(Message.BOOLEAN_VALUE, ""+couldUpdateResident);
			List<Map<String,String>> retValWrapper = new LinkedList<Map<String,String>>();
			retValWrapper.add(resultWrapper);

			// return wrapped value
			return retValWrapper;
		}
	}
		
	private DBController transactor; // database-system's controller
	private Map<String, DBquery> queriesHandler; // used to perform client's queries
	
	/**
	 * Constructor- creates a new controller-adapter object
	 * @param transactor- controller object for database-system
	 */
	public ServerDBControllerAdapter(DBController transactor) {	
		 
		// set database-system's controller
		this.transactor = transactor;
		
		// initialize the query handler with matching object for each query
		queriesHandler = new HashMap<String, ServerDBControllerAdapter.DBquery>();
		queriesHandler.put(Message.GET_VACANT_FLATS, new getVacantFlats());
		queriesHandler.put(Message.GET_VACANT_FLATS_BY_ROOM, new getVacantFlatsByRooms());
		queriesHandler.put(Message.GET_VACANT_FLATS_BY_ROOM_AND_ADDRESS, new getVacantFlatsByRoomsAndAddress());
		queriesHandler.put(Message.GET_FLATS_BY_ADDRESS, new getFlatsByAddress());
		queriesHandler.put(Message.GET_ALL_FLATS, new getAllFlats());
		queriesHandler.put(Message.GET_ALL_PENTHOUSES, new getAllPenthouses());
		queriesHandler.put(Message.GET_ALL_GARDEN_APARTMENTS, new getAllGardenApartments());
		queriesHandler.put(Message.GET_VACANT_APARTMENTS_NUM, new getVacantAprtsNum());
		queriesHandler.put(Message.GET_VACANT_APARTMENTS_NUM_BY_ADDRESS, new getVacantAprtsNumByAddress());
		queriesHandler.put(Message.ADD_BUILDINGS, new addBuildings());
		queriesHandler.put(Message.UPDATE_RESIDENT, new updateResident());
	}
	
	/**
	 * Extracts from given message client's query and query's parameters,
	 * and uses the query handler in order to perform the query at database-system.
	 * If handling the query had caused a database exception, null is returned
	 * @param fromClientMsg- message sent from client
	 * @return message holding query's result
	 */
	public Message handleClientQuery(Message fromClientMsg) {

		try {
			
			// get result according to specified query
			List<Map<String, String>> toClientMsgContent = queriesHandler.get(
					fromClientMsg.getQuery()).doQuery(
					fromClientMsg.getParameters(),
					fromClientMsg.getMultipleParameters(), transactor);
			
			// return answer message
			return new Message("", new HashMap<String, String>(), toClientMsgContent);
			
		} catch (DBException e) {
			
			// no message to return
			return null;
		}
	}
	
	/**
	 * Gets flats and extract the parameters of each flat, in order to store them in a map.
	 * The map's keys are according to Message's defined values
	 * @param flatsToConvert- flats to be stored as map of parameters
	 * @return list of parameters map for each flat
	 */
	private List< Map<String,String> > convertFlatsToParamsMap(List<Flat> flatsToConvert) {
		
		// initialize list to hold flats' parameters
		List< Map<String, String> > flatsParameters = new LinkedList<Map<String,String>>();
		
		// store each flat's parameters in map
		for (Flat currFlat : flatsToConvert) {
			
			// initialize map to hold current flat's parameters
			Map<String, String> currFlatParameters = new HashMap<String, String>();
			
			// get comment apartment's parameters
			convertApartmentsToParamMap(currFlatParameters, currFlat);
			
			// add current flat's parameters
			currFlatParameters.put(ParameterParser.FLOOR, ""+currFlat.getFloor());			
			currFlatParameters.put(ParameterParser.STORAGE_AREA, ""+currFlat.getWarehouse());
			
			// update type
			currFlatParameters.put(ParameterParser.TYPE, ParameterParser.FLAT);
			
			// add parameters to list
			flatsParameters.add(currFlatParameters);
		}
		
		// return parameters of retrieved flats
		return flatsParameters;
	}
	
	/**
	 * Gets penthouses and extract the parameters of each penthouse, in order to store them in a map.
	 * The map's keys are according to Message's defined values
	 * @param penthousesToConvert- penthouses to be stored as map of parameters
	 * @return list of parameters map for each penthouse
	 */
	private List< Map<String,String> > convertPenthousesToParamsMap(List<Penthouse> penthousesToConvert) {
		
		// initialize list to hold penthouses' parameters
		List< Map<String, String> > penthouseParameters = new LinkedList<Map<String,String>>();
		
		// store each penthouse's parameters in map
		for (Penthouse currPenthouse : penthousesToConvert) {
			
			// initialize map to hold current penthouse's parameters
			Map<String, String> currPenthouseParameters = new HashMap<String, String>();
			
			// get comment apartment's parameters
			convertApartmentsToParamMap(currPenthouseParameters, currPenthouse);
			
			// add current penthouse's parameters
			currPenthouseParameters.put(ParameterParser.FLOOR, ""+currPenthouse.getFloor());			
			currPenthouseParameters.put(ParameterParser.BALCONY_AREA, ""+currPenthouse.getBalconySize());
			
			// update type
			currPenthouseParameters.put(ParameterParser.TYPE, ParameterParser.PENTHOUSE);
			
			// add parameters to list
			penthouseParameters.add(currPenthouseParameters);
		}
		
		// return parameters of retrieved flats
		return penthouseParameters;
	}
	
	/**
	 * Gets garden apartments and extract the parameters of each garden apartment, in order to store them in a map.
	 * The map's keys are according to Message's defined values
	 * @param gardenApartmentsToConvert- garden apartments to be stored as map of parameters
	 * @return list of parameters map for each garden apartment
	 */
	private List< Map<String,String> > convertGardenApartmentsToParamsMap(List<GardenApartment> gardenApartmentsToConvert) {
		
		// initialize list to hold garden apartments' parameters
		List< Map<String, String> > gardenApartmentsParameters = new LinkedList<Map<String,String>>();
		
		// store each garden apartments's parameters in map
		for (GardenApartment currgardenApartment : gardenApartmentsToConvert) {
			
			// initialize map to hold current garden apartment's parameters
			Map<String, String> currGardenApartmentParameters = new HashMap<String, String>();
			
			// get comment apartment's parameters
			convertApartmentsToParamMap(currGardenApartmentParameters, currgardenApartment);
			
			// add current garden apartments's parameters
			currGardenApartmentParameters.put(ParameterParser.SEPARATE_ENTRANCE, ""+currgardenApartment.hasSepEntrance());			
			currGardenApartmentParameters.put(ParameterParser.GARDEN_AREA, ""+currgardenApartment.getGardenArea());
			
			// update type
			currGardenApartmentParameters.put(ParameterParser.TYPE, ParameterParser.GARDEN_APARTMENT);
			
			// add parameters to list
			gardenApartmentsParameters.add(currGardenApartmentParameters);
		}
		
		// return parameters of retrieved flats
		return gardenApartmentsParameters;
	}
	
	/**
	 * Adds to given parameters map the parameters of given apartment that are common
	 * to all derived types.
	 * The map's keys are according to Message's defined values
	 * @param apartmentParameters- map to hold the parameters
	 * @param apartment- apartment to be processed
	 */
	private void convertApartmentsToParamMap(Map<String, String> apartmentParameters, Apartment apartment) {
		
		// add given apartment's parameters
		apartmentParameters.put(ParameterParser.ADDRESS, ""+apartment.getAddress());
		apartmentParameters.put(ParameterParser.APARTMENT_NUMBER, ""+apartment.getApartmentNum());
		apartmentParameters.put(ParameterParser.RESIDENT_NAME, apartment.getName());
		apartmentParameters.put(ParameterParser.ROOM_NUMBER, ""+apartment.getRoomNum());
		apartmentParameters.put(ParameterParser.SIZE, ""+apartment.getSize());
		apartmentParameters.put(ParameterParser.TOTAL_AREA, ""+apartment.totalArea());
	}
}